#!/usr/bin/env python
#
# Copyright (c) 2008-2010, 2013 Wind River Systems, Inc.
#
# SPDX-License-Identifier: LGPL-2.1-only
#
"""convert tables.in files to enums, tables, and support code.

Inputs are a type name, prefix, and a list of columns, followed by a list of
names with optional "= value" suffixes, plus optional additional columns.
The names are used to create enums and a table of strings, as well as
to/from lookups between the ids and names.  If additional columns are
defined, each column (separated by ", ") is used to create an additional
table of the given name, and a lookup function from ids.  Example:
        foo: FFF; const char *bar = "GOZINTA"
        hello, "yah"
        world, "nope"
produces:
        typedef enum {
                FFF_UNKNOWN = -1,
                FFF_MIN = 0,
                FFF_NONE = 0,
                FFF_HELLO,
                FFF_WORLD,
                FFF_MAX
        } foo_id_t;
        extern const char *foo_name(foo_id_t id);
        extern foo_id_t foo_id(const char *name);
        extern const char *foo_bar(foo_id_t id);

such that foo_name(1) => "hello" and foo_bar(1) => "yah".  If there
is an assigned value for a column description, missing column values
yield that value, otherwise they yield "unknown".

Values out of range yield "unknown", and unrecognized names yield the
value -1.  Note that the "MAX" value is one more than the highest defined
value.  (This is for consistency with C array bounds.)
"""

import glob
import sys
import string
import os
from templatefile import TemplateFile

class DataType:
    """a set of related DataItem objects"""

    def __init__(self, path):
        """read the first line of path, then make tuples of the rest"""
        with open(path,'r') as source:
            definition = source.readline().rstrip()
            self.name, qualifiers = definition.split(': ', 2)
            if '; ' in qualifiers:
                self.prefix, columns = qualifiers.split('; ')
            else:
                self.prefix = qualifiers
                columns = []
            self.flags = False
            if len(columns):
                self.columns = []
                columns = columns.split(', ')
                for col in columns:
                    indexed = False
                    if col.startswith("FLAGS"):
                        print("Flags: set for %s" % self.name)
                        self.flags = True
                        continue
                    if col.startswith("INDEXED "):
                        col = col[8:]
                        indexed = True
                    if "=" in col:
                        name, default = col.split(' = ')
                    else:
                        name, default = col, ""
                    if " " in name:
                        words = name.split(' ')
                        name = words[-1]
                        del words[-1]
                        type = ' '.join(words)
                    else:
                        type = "char *"
                    self.columns.append({"indexed":indexed, "type":type, "name":name, "value":default})
            else:
                self.columns = []
            self.data = []
            self.comments = []
            index = 1
            for line in source.readlines():
                item = {}
                if line.startswith('#'):
                    self.comments.append(line.rstrip().replace('#', ''))
                    continue
                # first entry on the line is the "real" name/id, following hunks
                # are additional columns
                cols = line.rstrip().split(', ')
                item["name"] = cols.pop(0)
                item["upper"] = item["name"].replace('-', '_').upper()
                column_list = []
                for col in self.columns:
                    if len(cols) > 0:
                        value = cols.pop(0)
                        if col["indexed"]:
                            if not "max" in col:
                                col["max"] = value
                            if value > col["max"]:
                                col["max"] = value
                            if not "min" in col:
                                col["min"] = value
                            if value < col["min"]:
                                col["min"] = value
                        column_list.append({"name":col["name"], "value":value})
                    else:
                        column_list.append({"name":col["name"], "value":col["value"]})
                item["cols"] = column_list
                item["index"] = index
                index = index + 1
                self.data.append(item)

    def __getitem__(self, key):
        """Make this object look like a dict for Templates to use"""
        attr = getattr(self, key)

        if callable(attr):
            return attr()
        else:
            return attr

    def __repr__(self):
        column = 0
        out = ""
        out += "type: %s_t" % self.name
        out += " (prefix '%s_ENUM')\n" % self.prefix
        for col in self.columns:
            out += "  extra column: %s %s (default %s)\n" % (col["type"], col["name"], col["value"])
        out += "   "
        for item in self.data:
            column = column + 1
            if column > 4 and column % 4 == 1:
                out += "\n   "
            out += "%-19s" % item["name"]
#           for col in item["cols"]:
#               out += "\t%s(%s)\n" % (col["name"], col["value"])
        return out

    def comment(self):
        if len(self.comments):
            return '/*' + '\n *'.join(self.comments) + ' */\n'
        else:
            return ''

    def names(self):
        return ',\n\t'.join('"%s"' % x["name"] for x in self.data)

    def enums(self):
        return ',\n\t'.join('%s_%s' % (self.prefix, x["upper"]) for x in self.data)

    def flag_enums(self):
        if not self.flags:
            return ""
        enum_lines = []
        enum_lines.append('typedef enum {') 
        prefix = self.prefix + 'F'
        for x in self.data:
            enum_lines.append('\t%s_%s = (1 << %s_%s),' %
                (prefix, x["upper"], self.prefix, x["upper"]))
        enum_lines.append('} pseudo_%s_f;' % self.name)
        return '\n'.join(enum_lines)

    def column_names(self):
        decl_lines = []
        column = 0
        for col in self.columns:
            decl_lines.append("static %s %s_id_to_%s[] = {" % (col["type"], self.name, col["name"]))
            decl_lines.append('\t%s,' % col["value"])
            for item in self.data:
                decl_lines.append('\t%s,' % item["cols"][column]["value"])
            decl_lines.append('\t0')
            decl_lines.append("};")
            if col["indexed"]:
                decl_lines.append("static int %s_%s_to_id[] = {" % (self.name, col["name"]))
                for item in self.data:
                    decl_lines.append('\t[%s] = %d,' % (item["cols"][column]["value"], item["index"]))
                decl_lines.append("};")
            column = column + 1
        return '\n'.join(decl_lines)

    def column_funcs(self):
        decl_lines = []
        for col in self.columns:
            decl_lines.append('extern %s' % col["type"])
            decl_lines.append('pseudo_%s_%s(pseudo_%s_t id) {' %
                (self.name, col["name"], self.name))
            decl_lines.append('\tif (id < 0 || id >= %s_MAX)' % (self.prefix))
            decl_lines.append('\t\treturn %s;' % col["value"])
            decl_lines.append('\treturn %s_id_to_%s[id];' %
                (self.name, col["name"]))
            decl_lines.append('}')
            if col["indexed"]:
                table_name = '%s_%s_to_id' % (self.name, col["name"])
                decl_lines.append('extern int')
                decl_lines.append('pseudo_%s_%s_id(%s val) {' %
                    (self.name, col["name"], col["type"]))
                decl_lines.append('\tif ((val < %s) || (val > %s)) {' % (col["min"], col["max"]))
                decl_lines.append('\t\treturn -1;')
                decl_lines.append('\t}')
                decl_lines.append('\tif (%s[val] != 0) {' % table_name)
                decl_lines.append('\t\treturn %s[val];' % table_name)
                decl_lines.append('\t}')
                decl_lines.append('\treturn -1;')
                decl_lines.append('}')
        return '\n'.join(decl_lines)

    def column_protos(self):
        decl_lines = []
        for col in self.columns:
            decl_lines.append('extern %s pseudo_%s_%s(pseudo_%s_t id);' %
                (col["type"], self.name, col["name"], self.name))
            if col["indexed"]:
                decl_lines.append('extern int pseudo_%s_%s_id(%s val);' %
                    (self.name, col["name"], col["type"]))
        return '\n'.join(decl_lines)

def main():
    """Read in function defintions, write out files based on templates."""
    datatypes = []
    templates = []

    # error checking helpfully provided by the exception handler
    copyright_file = open('guts/COPYRIGHT')
    TemplateFile.copyright = copyright_file.read()
    copyright_file.close()

    for path in glob.glob('table_templates/*'):
        try:
            template_file = TemplateFile(path)
            template_file.emit('copyright')
            template_file.emit('header')
            templates.append(template_file)
        except IOError:
            print("Invalid or malformed template %s.  Aborting." % path)
            exit(1)

    for filename in sys.argv[1:]:
        # read in the datatype
        sys.stdout.write("%s: " % filename)
        datatype = DataType(filename)
        datatypes.append(datatype)
        print(datatype.__repr__())
        print("")

    print("Writing datatypes...")
    for datatype in datatypes:
        # populate various tables and files with each datatype
        for template_file in templates:
            template_file.emit('body', datatype)
    print("done.  Cleaning up.")
    
    for template_file in templates:
        # clean up files
        template_file.emit('footer')
        template_file.close()

if __name__ == '__main__':
    main()
